package com.kfyang.manualgear;

import com.kfyang.manualgear.annotation.global.DaoConfig;
import com.kfyang.manualgear.constant.ClassConstants;
import com.kfyang.manualgear.generator.declare.ClassWrapper;
import com.kfyang.manualgear.util.GlobalConfig;
import com.kfyang.manualgear.util.GlobalConfigUtils;
import com.kfyang.manualgear.util.GlobalInitializer;
import com.kfyang.manualgear.util.StringUtils;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static com.kfyang.manualgear.constant.Constants.STAR;
import static com.kfyang.manualgear.util.GlobalConfigUtils.elementUtils;

/**
 * 先读取 DaoConfig 注解的配置信息
 * 然后以 Dao 注解为入口生成接口方法的实现
 */
@SuppressWarnings("unused")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@SupportedAnnotationTypes(ClassConstants.DAO_ANNOTATION)
public class DaoProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {

        GlobalConfigUtils.typeUtils = processingEnv.getTypeUtils();
        elementUtils = processingEnv.getElementUtils();
        GlobalConfigUtils.filer = processingEnv.getFiler();
        GlobalConfigUtils.messager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (annotations.isEmpty()) {
            return false;
        }
        GlobalConfigUtils.printBanner();

        long record = System.currentTimeMillis();

        new GlobalInitializer(roundEnv.getElementsAnnotatedWith(DaoConfig.class)).initEnv();

        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotations.iterator().next());

        // 处理每个标注 Dao 注解的接口
        elements.stream()
            .filter(element -> element.getKind() == ElementKind.INTERFACE)
            .forEach(this::generateFile);

        if (GlobalConfig.showTime) {
            System.out.println("Total process time: " + (System.currentTimeMillis() - record) + " ms");
        }

        return false; // 未修改抽象语法树
    }


    /**
     * 生成实现类文件
     *
     * @param element 标注了 Dao 注解的接口
     */
    private void generateFile(Element element) {
        TypeElement interfaceElement = (TypeElement) element;
        TypeMirror entityType = getEntityType(element);
        ClassWrapper classWrapper = GlobalConfig.engine.getClassSpecGenerator(element).create();

        // 生成所有方法并加到类中
        List<MethodSpec> methods = new ArrayList<>();
        interfaceElement.getEnclosedElements().stream()
            .filter(member -> member.getKind() == ElementKind.METHOD)
            .forEach(methodElement -> generateMethod(methods, (ExecutableElement) methodElement, classWrapper, entityType != null));

        List<ExecutableElement> executableElements = interfaceElement.getEnclosedElements().stream()
            .filter(member -> member.getKind() == ElementKind.METHOD)
            .map(member -> ((ExecutableElement) member))
            .toList();
        List<MethodSpec> ormMethods = GlobalConfig.engine.getOrmGenerator(executableElements, entityType).build();
        methods.addAll(ormMethods);
        classWrapper.addMethods(methods);

        // 输出到文件
        String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
        JavaFile javaFile = JavaFile.builder(packageName, classWrapper.build()).indent("\t")
            .skipJavaLangImports(true)
            .addStaticImport(StringUtils.class, STAR)
            .build();
        try {
            javaFile.writeTo(GlobalConfigUtils.filer);
        } catch (IOException e) {
            throw new RuntimeException("文件生成失败");
        }
    }

    /**
     * 获取泛型类型
     * @param element 接口
     * @return entityType
     */
    private TypeMirror getEntityType(Element element) {
        TypeMirror entityType;
        var entrySet = element.getAnnotationMirrors()
            .stream().filter(annotationMirror -> ClassConstants.DAO_ANNOTATION.equals(annotationMirror.getAnnotationType().toString()))
            .toList().get(0)
            .getElementValues().entrySet();
        if (!entrySet.isEmpty()) {
            entityType = ((TypeMirror) entrySet.iterator().next().getValue().getValue());
        } else {
            entityType = null;
        }
        return entityType;
    }

    /**
     * 生成方法
     *
     * @param methods       方法集合
     * @param methodElement 方法元素
     * @param classWrapper  所在类信息
     */
    private void generateMethod(List<MethodSpec> methods, ExecutableElement methodElement, ClassWrapper classWrapper, boolean useSimple) {
        MethodSpec methodSpec = GlobalConfig.engine.getQueryMethodGenerator(methodElement, classWrapper, useSimple).build();
        if (methodSpec == null) {
            methodSpec = GlobalConfig.engine.getModifyMethodGenerator(methodElement, classWrapper, useSimple).build();
        }
        if (methodSpec != null) {
            methods.add(methodSpec);
        }
    }
}
